package com.xuan.judge.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuan.api.domain.UserExeResult;
import com.xuan.api.domain.dto.JudgeSubmitDTO;
import com.xuan.api.domain.vo.UserQuestionResultVo;
import com.xuan.common.core.constants.CacheConstants;
import com.xuan.common.core.constants.Constants;
import com.xuan.common.core.constants.JudgeConstants;
import com.xuan.common.core.enums.CodeRunStatus;
import com.xuan.common.core.enums.QuestionStatus;
import com.xuan.common.redis.service.RedisService;
import com.xuan.judge.domain.SandBoxExecResult;
import com.xuan.judge.domain.UserSubmit;
import com.xuan.judge.mapper.UserSubmitMapper;
import com.xuan.judge.service.JudgeService;
import com.xuan.judge.service.SandBoxPoolService;
import com.xuan.judge.service.SandBoxService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class JudgeServiceImpl implements JudgeService {


    // TODO 这个要改成容器池版本
    @Resource
    private SandBoxService sandBoxService;

    @Resource
    private SandBoxPoolService sandBoxPoolService;

    @Resource
    private UserSubmitMapper userSubmitMapper;

    @Resource
    private RedisService redisService;

    @Override
    public UserQuestionResultVo doJudgeJavaCode(JudgeSubmitDTO judgeSubmitDTO) {
        Long userId = judgeSubmitDTO.getUserId();
        String execCode = judgeSubmitDTO.getExecCode();
        List<String> inputList = judgeSubmitDTO.getInputList();
        // 1. 使用 docker 沙箱判题服务,执行代码获取执行结果
        SandBoxExecResult sandBoxExecResult = sandBoxPoolService.execJavaCode(userId, execCode, inputList);
        // 2. 根据 sandBoxExecResult 判断运行是否正确
        UserQuestionResultVo userQuestionResultVo = new UserQuestionResultVo();     // 返回结果
        CodeRunStatus codeRunStatus = sandBoxExecResult.getCodeRunStatus();
        if (CodeRunStatus.SUCCESS.equals(codeRunStatus)) {
            // 判断运行结果是否完全正确, 时间限制, 空间限制
            userQuestionResultVo = doJudgeRunResult(sandBoxExecResult, judgeSubmitDTO, userQuestionResultVo);
        } else {
            if (codeRunStatus == null) {
                userQuestionResultVo.setExeMessage(CodeRunStatus.UNKNOWN_FAILED.getMsg());
            } else {
                // 将代码沙箱的执行结果进行赋值
                userQuestionResultVo.setExeMessage(sandBoxExecResult.getExecMessage());
            }
            userQuestionResultVo.setPass(Constants.FALSE);
            userQuestionResultVo.setScore(JudgeConstants.ERROR_SCORE);
        }

        // 3. 保存结果到数据库并更新 redis缓存(u:q:p:l: 和 u:q:f:l:)
        saveUserSubmit(judgeSubmitDTO, userQuestionResultVo);
        updateUserQuestionCache(codeRunStatus, judgeSubmitDTO);
        return userQuestionResultVo;
    }


    private UserQuestionResultVo doJudgeRunResult(SandBoxExecResult sandBoxExecResult,
                                                  JudgeSubmitDTO judgeSubmitDTO,
                                                  UserQuestionResultVo userQuestionResultVo) {
        // 预期结果
        List<String> outputList = judgeSubmitDTO.getOutputList();
        // 实际执行结果
        List<String> execOutputList = sandBoxExecResult.getExecOutputList();

        // 1) 先判断输出结果是否完全一致  2) 判断时间是否超过限制  3) 判断空间是否超过限制
        if (outputList.size() != execOutputList.size()) {
            userQuestionResultVo.setPass(Constants.FALSE);
            userQuestionResultVo.setExeMessage(CodeRunStatus.NOT_ALL_PASSED.getMsg());
            userQuestionResultVo.setScore(JudgeConstants.ERROR_SCORE);
            return userQuestionResultVo;
        }

        boolean isPassed = compareResult(judgeSubmitDTO, userQuestionResultVo, outputList, execOutputList);
        return assembleResult(judgeSubmitDTO, userQuestionResultVo, isPassed, sandBoxExecResult);
    }


    private boolean compareResult(JudgeSubmitDTO judgeSubmitDTO,
                                  UserQuestionResultVo userQuestionResultVo,
                                  List<String> outputList, List<String> execOutputList) {
        // 判断执行结果和预期结果是否一致
        boolean isPassed = true;
        List<UserExeResult> userExeResultList = new ArrayList<>();
        List<String> inputList = judgeSubmitDTO.getInputList();
        for (int i = 0, sz = outputList.size(); i < sz; i++) {
            String output = outputList.get(i);
            String execOutput = execOutputList.get(i);
            if (!Objects.equals(output, execOutput)) {
                isPassed = false;
            }
            UserExeResult userExeResult = new UserExeResult();
            userExeResult.setInput(inputList.get(i));
            userExeResult.setOutput(output);
            userExeResult.setExeOutput(execOutput);
            userExeResultList.add(userExeResult);
        }
        userQuestionResultVo.setUserExeResultList(userExeResultList);
        return isPassed;
    }


    private UserQuestionResultVo assembleResult(JudgeSubmitDTO judgeSubmitDTO, UserQuestionResultVo userQuestionResultVo,
                                                boolean isPassed, SandBoxExecResult sandBoxExecResult) {
        // 比较时间和空间限制,并返回 UserQuestionResultVo
        Integer timeLimit = judgeSubmitDTO.getTimeLimit();
        Integer spaceLimit = judgeSubmitDTO.getSpaceLimit();
        Long useTime = sandBoxExecResult.getUseTime();
        Long useMemory = sandBoxExecResult.getUseMemory();

        if (!isPassed) {
            userQuestionResultVo.setPass(Constants.FALSE);
            userQuestionResultVo.setExeMessage(CodeRunStatus.NOT_ALL_PASSED.getMsg());
            userQuestionResultVo.setScore(JudgeConstants.ERROR_SCORE);
            return userQuestionResultVo;
        }

        if (useTime > timeLimit) {
            userQuestionResultVo.setPass(Constants.FALSE);
            userQuestionResultVo.setScore(JudgeConstants.ERROR_SCORE);
            userQuestionResultVo.setExeMessage(CodeRunStatus.OUT_OF_TIME.getMsg());
            return userQuestionResultVo;
        }
        if (useMemory > spaceLimit) {
            userQuestionResultVo.setPass(Constants.FALSE);
            userQuestionResultVo.setScore(JudgeConstants.ERROR_SCORE);
            userQuestionResultVo.setExeMessage(CodeRunStatus.OUT_OF_MEMORY.getMsg());
            return userQuestionResultVo;
        }

        userQuestionResultVo.setPass(Constants.TRUE);
        Integer score = JudgeConstants.DEFAULT_SCORE * judgeSubmitDTO.getDifficulty();
        userQuestionResultVo.setScore(score);
        return userQuestionResultVo;
    }

    private void saveUserSubmit(JudgeSubmitDTO judgeSubmitDTO, UserQuestionResultVo userQuestionResultVo) {
        // 1) 先删除用户原先提交的记录 2) 再往数据库插入数据
        Long userId = judgeSubmitDTO.getUserId();
        Long contestId = judgeSubmitDTO.getContestId();
        Long questionId = judgeSubmitDTO.getQuestionId();

        userSubmitMapper.delete(new LambdaQueryWrapper<UserSubmit>()
                .eq(UserSubmit::getUserId, userId)
                .eq(UserSubmit::getQuestionId, questionId)
                .eq(contestId != null, UserSubmit::getContestId, contestId)
                .isNull(contestId == null, UserSubmit::getContestId));
        UserSubmit userSubmit = new UserSubmit();
        userSubmit.setUserId(userId);
        userSubmit.setQuestionId(questionId);
        userSubmit.setContestId(contestId);
        userSubmit.setUserCode(judgeSubmitDTO.getUserCode());
        userSubmit.setPass(userQuestionResultVo.getPass());
        userSubmit.setExecMessage(userQuestionResultVo.getExeMessage());
        userSubmit.setScore(userQuestionResultVo.getScore());
        userSubmit.setProgramType(judgeSubmitDTO.getProgramType());
        // 将代码执行结果转换为 json 字符串存储到数据库中
        String userExecResultJson = JSONUtil.toJsonStr(userQuestionResultVo.getUserExeResultList());
        userSubmit.setCaseJudgeRes(userExecResultJson);
        // 不能自动插入(因为 ThreadLocal 是跨服务调用的)
        userSubmit.setCreateBy(userId);
        userSubmitMapper.insert(userSubmit);
    }

    private void updateUserQuestionCache(CodeRunStatus codeRunStatus, JudgeSubmitDTO judgeSubmitDTO) {
        Long userId = judgeSubmitDTO.getUserId();
        Long questionId = judgeSubmitDTO.getQuestionId();
        String questionPassListKey = getUserQuestionStatusListKey(userId, QuestionStatus.PASSED.getValue());
        String questionFailListKey = getUserQuestionStatusListKey(userId, QuestionStatus.FAILED.getValue());
        if (CodeRunStatus.SUCCESS.equals(codeRunStatus)) {
            redisService.leftPushForList(questionPassListKey, questionId);
            redisService.removeForList(questionFailListKey, questionId);
        } else {
            redisService.leftPushForList(questionFailListKey, questionId);
            redisService.removeForList(questionPassListKey, questionId);
        }
    }

    private String getUserQuestionStatusListKey(Long userId, Integer status) {
        if (Objects.equals(status, QuestionStatus.PASSED.getValue())) {
            return CacheConstants.USER_QUESTION_PASS_LIST_KEY + userId;
        } else if (Objects.equals(status, QuestionStatus.FAILED.getValue())) {
            return CacheConstants.USER_QUESTION_FAIL_LIST_KEY + userId;
        }
        return "";
    }
}
